﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;


namespace advadev.Windows.Graph
{

    /// <summary>
    /// Represents a graph
    /// </summary>
    internal class Graph : Grid
    {

        /// <summary>for internal use</summary>
        private EventHandler _onGraphNodeIndexChangedEventHandler;
        /// <summary>for internal use</summary>
        private List<UIElement> _layerElements = new List<UIElement>();
        /// <summary>for internal use</summary>
        private SortedDictionary<int, List<UIElement>> _nodeElements = new SortedDictionary<int, List<UIElement>>();


        /// <summary>
        /// Initializes a new instance of the Graph class
        /// </summary>
        internal Graph()
        {
            _onGraphNodeIndexChangedEventHandler = new EventHandler((object sender, EventArgs args) =>
            {
                RemoveNodeElement(sender as UIElement);
                AddNodeElement(sender as UIElement);
            });
        }


        #region attached properties
        static internal readonly DependencyProperty GraphNodeIndexProperty = DependencyProperty.RegisterAttached("GraphNodeIndex", typeof(int), typeof(Graph), new UIPropertyMetadata(0));
        static internal int GetGraphNodeIndex(DependencyObject obj)
        {
            return (int)obj.GetValue(GraphNodeIndexProperty);
        }
        static internal void SetGraphNodeIndex(DependencyObject obj, int value)
        {
            obj.SetValue(GraphNodeIndexProperty, value);
        }
        #endregion attached properties


        #region dependency properties
        static internal readonly DependencyProperty GraphNameProperty = DependencyProperty.Register("GraphName", typeof(string), typeof(Graph), new UIPropertyMetadata(string.Empty));
        internal string GraphName
        {
            get { return (string)GetValue(GraphNameProperty); }
            set { SetValue(GraphNameProperty, value); }
        }
        #endregion dependency properties


        /// <summary>
        /// Gets a flag indicating whether the graph has node elements
        /// </summary>
        internal bool HasNodeElements
        {
            get
            {
                return _nodeElements.Count > 0;
            }
        }


        /// <summary>
        /// Adds the given node element
        /// </summary>
        /// <param name="nodeElement">
        /// Node element
        /// </param>
        internal void AddNodeElement(UIElement nodeElement)
        {
            if (nodeElement != null)
            {
                int graphNodeIndex = GraphContainer.GetGraphNodeIndex(nodeElement);
                if (!_nodeElements.ContainsKey(graphNodeIndex))
                {
                    _nodeElements.Add(graphNodeIndex, new List<UIElement>() { nodeElement });
                    SetGraphNodeIndex(nodeElement, graphNodeIndex);
                    registerGraphNodeIndexChangedHandler(nodeElement);
                    nodeElement.IsVisibleChanged += new DependencyPropertyChangedEventHandler(nodeElement_IsVisibleChanged);
                    if (nodeElement.IsVisible)
                    {
                        foreach (UIElement predecessor in getAllVisiblePredecessor(graphNodeIndex))
                        {
                            foreach (UIElement successor in getAllVisibleSuccessor(graphNodeIndex))
                            {
                                removeLine(predecessor, successor);
                            }
                        }
                        foreach (UIElement predecessor in getAllVisiblePredecessor(graphNodeIndex))
                        {
                            addLine(predecessor, nodeElement);
                        }
                        foreach (UIElement successor in getAllVisibleSuccessor(graphNodeIndex))
                        {
                            addLine(nodeElement, successor);
                        }
                    }
                }
                else
                {
                    if (!_nodeElements[graphNodeIndex].Contains(nodeElement))
                    {
                        _nodeElements[graphNodeIndex].Add(nodeElement);
                        SetGraphNodeIndex(nodeElement, graphNodeIndex);
                        registerGraphNodeIndexChangedHandler(nodeElement);
                        nodeElement.IsVisibleChanged += new DependencyPropertyChangedEventHandler(nodeElement_IsVisibleChanged);
                        if (nodeElement.IsVisible)
                        {
                            foreach (UIElement predecessor in getAllVisiblePredecessor(graphNodeIndex))
                            {
                                addLine(predecessor, nodeElement);
                            }
                            foreach (UIElement successor in getAllVisibleSuccessor(graphNodeIndex))
                            {
                                addLine(nodeElement, successor);
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Removes the given node element
        /// </summary>
        /// <param name="nodeElement">
        /// Node element
        /// </param>
        internal void RemoveNodeElement(UIElement nodeElement)
        {
            if (nodeElement != null)
            {
                int graphNodeIndex = Graph.GetGraphNodeIndex(nodeElement);
                if (_nodeElements.ContainsKey(graphNodeIndex))
                {
                    if (_nodeElements[graphNodeIndex].Contains(nodeElement))
                    {
                        if (nodeElement.IsVisible)
                        {
                            foreach (UIElement predecessor in getAllVisiblePredecessor(graphNodeIndex))
                            {
                                removeLine(predecessor, nodeElement);
                            }
                            foreach (UIElement successor in getAllVisibleSuccessor(graphNodeIndex))
                            {
                                removeLine(nodeElement, successor);
                            }
                        }
                        nodeElement.IsVisibleChanged -= new DependencyPropertyChangedEventHandler(nodeElement_IsVisibleChanged);
                        unregisterGraphNodeIndexChangedHandler(nodeElement);
                        SetGraphNodeIndex(nodeElement, 0);
                        _nodeElements[graphNodeIndex].Remove(nodeElement);
                        if (_nodeElements[graphNodeIndex].Count == 0)
                        {
                            if (nodeElement.IsVisible)
                            {
                                foreach (UIElement predecessor in getAllVisiblePredecessor(graphNodeIndex))
                                {
                                    foreach (UIElement successor in getAllVisibleSuccessor(graphNodeIndex))
                                    {
                                        addLine(predecessor, successor);
                                    }
                                }
                            }
                            _nodeElements.Remove(graphNodeIndex);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Adds the given layer element
        /// </summary>
        /// <param name="layerElement">
        /// Layer element
        /// </param>
        internal void AddLayerElement(UIElement layerElement)
        {
            if (layerElement != null && !_layerElements.Contains(layerElement))
            {
                _layerElements.Add(layerElement);
                foreach (GraphSegmentContainer graphSegmentContainer in InternalChildren)
                {
                    graphSegmentContainer.AddLayerElement(layerElement);
                }
            }
        }

        /// <summary>
        /// Removes the given layer element
        /// </summary>
        /// <param name="layerElement">
        /// Layer element
        /// </param>
        internal void RemoveLayerElement(UIElement layerElement)
        {
            if (layerElement != null && _layerElements.Contains(layerElement))
            {
                foreach (GraphSegmentContainer graphSegmentContainer in InternalChildren)
                {
                    graphSegmentContainer.RemoveLayerElement(layerElement);
                }
                _layerElements.Remove(layerElement);
            }
        }

        /// <summary>
        /// Handles the "IsVisibleChanged" event of a node element
        /// </summary>
        /// <param name="sender">
        /// Sender of the event
        /// </param>
        /// <param name="args">
        /// Event argument
        /// </param>
        private void nodeElement_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs args)
        {
            UIElement nodeElement = sender as UIElement;
            if (nodeElement != null)
            {
                if (nodeElement.IsVisible)
                {
                    int graphNodeIndex = Graph.GetGraphNodeIndex(nodeElement);
                    List<UIElement> elements = getAllVisibleElements(graphNodeIndex);
                    if (elements.Count == 1 && elements[0] == nodeElement)
                    {
                        foreach (UIElement predecessor in getAllVisiblePredecessor(graphNodeIndex))
                        {
                            foreach (UIElement successor in getAllVisibleSuccessor(graphNodeIndex))
                            {
                                removeLine(predecessor, successor);
                            }
                        }
                    }
                    foreach (UIElement predecessor in getAllVisiblePredecessor(graphNodeIndex))
                    {
                        addLine(predecessor, nodeElement);
                    }
                    foreach (UIElement successor in getAllVisibleSuccessor(graphNodeIndex))
                    {
                        addLine(nodeElement, successor);
                    }
                }
                else
                {
                    int graphNodeIndex = Graph.GetGraphNodeIndex(nodeElement);
                    List<UIElement> elements = getAllVisibleElements(graphNodeIndex);
                    foreach (UIElement predecessor in getAllVisiblePredecessor(graphNodeIndex))
                    {
                        removeLine(predecessor, nodeElement);
                    }
                    foreach (UIElement successor in getAllVisibleSuccessor(graphNodeIndex))
                    {
                        removeLine(nodeElement, successor);
                    }
                    if (elements.Count == 0)
                    {
                        foreach (UIElement predecessor in getAllVisiblePredecessor(graphNodeIndex))
                        {
                            foreach (UIElement successor in getAllVisibleSuccessor(graphNodeIndex))
                            {
                                addLine(predecessor, successor);
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Adds a line between the given start element and the given end
        /// element
        /// </summary>
        /// <param name="startElement">
        /// Start element
        /// </param>
        /// <param name="endElement">
        /// End element
        /// </param>
        private void addLine(UIElement startElement, UIElement endElement)
        {
            foreach (UIElement uiElement in InternalChildren)
            {
                GraphSegmentContainer graphSegmentContainer = uiElement as GraphSegmentContainer;
                if (graphSegmentContainer != null)
                {
                    if (graphSegmentContainer.StartElement == startElement && graphSegmentContainer.EndElement == endElement)
                    {
                        return;
                    }
                }
            }
            GraphSegmentContainer newGraphSegmentContainer = new GraphSegmentContainer();
            newGraphSegmentContainer.ParentGraph = this;
            newGraphSegmentContainer.StartElement = startElement;
            newGraphSegmentContainer.EndElement = endElement;
            foreach (UIElement layerElement in _layerElements)
            {
                newGraphSegmentContainer.AddLayerElement(layerElement);
            }
            InternalChildren.Add(newGraphSegmentContainer);
        }

        /// <summary>
        /// Removes the line between the given start element and the given end
        /// element
        /// </summary>
        /// <param name="startElement">
        /// Start element
        /// </param>
        /// <param name="endElement">
        /// End element
        /// </param>
        private void removeLine(UIElement startElement, UIElement endElement)
        {
            foreach (UIElement uiElement in InternalChildren)
            {
                GraphSegmentContainer graphSegmentContainer = uiElement as GraphSegmentContainer;
                if (graphSegmentContainer != null)
                {
                    if (graphSegmentContainer.StartElement == startElement && graphSegmentContainer.EndElement == endElement)
                    {
                        InternalChildren.Remove(graphSegmentContainer);
                        foreach (UIElement layerElement in _layerElements)
                        {
                            graphSegmentContainer.RemoveLayerElement(layerElement);
                        }
                        graphSegmentContainer.StartElement = null;
                        graphSegmentContainer.EndElement = null;
                        break;
                    }
                }
            }
        }

        /// <summary>
        /// Gets the graph segment style for the given node element
        /// </summary>
        /// <param name="nodeElement">
        /// Node element
        /// </param>
        internal Style GetGraphSegmentStyle(UIElement nodeElement)
        {
            Style graphStyle = FindResource(typeof(GraphSegment)) as Style;
            Style lastValidStyle = graphStyle;
            foreach (KeyValuePair<int, List<UIElement>> pair in _nodeElements)
            {
                if (pair.Value.Contains(nodeElement))
                {
                    Style startElementStyle = null;
                    FrameworkElement frameworkElement = nodeElement as FrameworkElement;
                    if (frameworkElement != null)
                    {
                        startElementStyle = frameworkElement.FindResource(typeof(GraphSegment)) as Style;
                    }
                    if (startElementStyle != null && startElementStyle != graphStyle)
                    {
                        lastValidStyle = startElementStyle;
                    }
                    break;
                }
                else
                {
                    foreach (UIElement uiElement in pair.Value)
                    {
                        if (uiElement.IsVisible)
                        {
                            FrameworkElement frameworkElement = uiElement as FrameworkElement;
                            if (frameworkElement != null)
                            {
                                Style currentStyle = frameworkElement.FindResource(typeof(GraphSegment)) as Style;
                                if (currentStyle != null && currentStyle != graphStyle)
                                {
                                    lastValidStyle = currentStyle;
                                    break;
                                }
                            }
                        }
                    }
                }
            }
            return lastValidStyle;
        }

        /// <summary>
        /// Registers a graph node index changed handler for the given node
        /// element
        /// </summary>
        /// <param name="nodeElement">
        /// Node element
        /// </param>
        private void registerGraphNodeIndexChangedHandler(UIElement nodeElement)
        {
            DependencyPropertyDescriptor graphNodeIndexDescriptor = DependencyPropertyDescriptor.FromProperty(GraphContainer.GraphNodeIndexProperty, typeof(Graph));
            if (graphNodeIndexDescriptor != null)
            {
                graphNodeIndexDescriptor.AddValueChanged(nodeElement, _onGraphNodeIndexChangedEventHandler);
            }
        }

        /// <summary>
        /// Unregisters a graph node index changed handler for the given node
        /// element
        /// </summary>
        /// <param name="nodeElement">
        /// Node element
        /// </param>
        private void unregisterGraphNodeIndexChangedHandler(UIElement nodeElement)
        {
            DependencyPropertyDescriptor graphNodeIndexDescriptor = DependencyPropertyDescriptor.FromProperty(GraphContainer.GraphNodeIndexProperty, typeof(Graph));
            if (graphNodeIndexDescriptor != null)
            {
                graphNodeIndexDescriptor.RemoveValueChanged(nodeElement, _onGraphNodeIndexChangedEventHandler);
            }
        }

        /// <summary>
        /// Gets a list of all visible predecessor elements for the given graph
        /// node index
        /// </summary>
        /// <param name="graphNodeIndex">
        /// Graph node index
        /// </param>
        /// <returns>
        /// List of all visible predecessor elements (or empty list if there are
        /// none)
        /// </returns>
        private List<UIElement> getAllVisiblePredecessor(int graphNodeIndex)
        {
            List<UIElement> result = new List<UIElement>();
            if (_nodeElements.ContainsKey(graphNodeIndex))
            {
                foreach (KeyValuePair<int, List<UIElement>> pair in _nodeElements)
                {
                    if (pair.Key == graphNodeIndex)
                    {
                        break;
                    }
                    List<UIElement> tmp = new List<UIElement>();
                    foreach (UIElement uiElement in pair.Value)
                    {
                        if (uiElement.IsVisible)
                        {
                            tmp.Add(uiElement);
                        }
                    }
                    if (tmp.Count > 0)
                    {
                        result.Clear();
                        result.AddRange(tmp);
                    }
                }
            }
            return result;
        }

        /// <summary>
        /// Gets a list of all visible elements with the given graph node index
        /// </summary>
        /// <param name="graphNodeIndex">
        /// Graph node index
        /// </param>
        /// <returns>
        /// List of all visible elements with the given graph node index (or
        /// empty list if there are none)
        /// </returns>
        private List<UIElement> getAllVisibleElements(int graphNodeIndex)
        {
            List<UIElement> result = new List<UIElement>();
            if (_nodeElements.ContainsKey(graphNodeIndex))
            {
                foreach (KeyValuePair<int, List<UIElement>> pair in _nodeElements)
                {
                    if (pair.Key == graphNodeIndex)
                    {
                        foreach (UIElement uiElement in pair.Value)
                        {
                            if (uiElement.IsVisible)
                            {
                                result.Add(uiElement);
                            }
                        }
                        break;
                    }
                }
            }
            return result;
        }

        /// <summary>
        /// Gets a list of all visible successor elements for the given graph
        /// node index
        /// </summary>
        /// <param name="graphNodeIndex">
        /// Graph node index
        /// </param>
        /// <returns>
        /// List of all visible successor elements (or empty list if there are
        /// none)
        /// </returns>
        private List<UIElement> getAllVisibleSuccessor(int graphNodeIndex)
        {
            List<UIElement> result = new List<UIElement>();
            if (_nodeElements.ContainsKey(graphNodeIndex))
            {
                bool found = false;
                foreach (KeyValuePair<int, List<UIElement>> pair in _nodeElements)
                {
                    if (found)
                    {
                        foreach (UIElement uiElement in pair.Value)
                        {
                            if (uiElement.IsVisible)
                            {
                                result.Add(uiElement);
                            }
                        }
                        if (result.Count > 0)
                        {
                            break;
                        }
                    }
                    else if (pair.Key == graphNodeIndex)
                    {
                        found = true;
                    }
                }
            }
            return result;
        }

    }

}
